package org.nobody.mallb2c.mallorder.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.nobody.common.exception.NoStockException;
import org.nobody.common.to.mq.OrderTo;
import org.nobody.common.to.mq.SeckillOrderTo;
import org.nobody.common.utils.R;
import org.nobody.common.vo.MemberRespVo;
import org.nobody.mallb2c.mallorder.constant.OrderConstant;
import org.nobody.mallb2c.mallorder.dao.OrderItemDao;
import org.nobody.mallb2c.mallorder.entity.OrderItemEntity;
import org.nobody.mallb2c.mallorder.entity.PaymentInfoEntity;
import org.nobody.mallb2c.mallorder.enume.OrderStatusEnum;
import org.nobody.mallb2c.mallorder.feign.CartFeignService;
import org.nobody.mallb2c.mallorder.feign.MemberFeignService;
import org.nobody.mallb2c.mallorder.feign.ProductFeignService;
import org.nobody.mallb2c.mallorder.feign.WmsFeignService;
import org.nobody.mallb2c.mallorder.interceptor.LoginUserInterceptor;
import org.nobody.mallb2c.mallorder.service.OrderItemService;
import org.nobody.mallb2c.mallorder.service.PaymentInfoService;
import org.nobody.mallb2c.mallorder.to.OrderCreateTo;
import org.nobody.mallb2c.mallorder.vo.*;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.nobody.common.utils.PageUtils;
import org.nobody.common.utils.Query;

import org.nobody.mallb2c.mallorder.dao.OrderDao;
import org.nobody.mallb2c.mallorder.entity.OrderEntity;
import org.nobody.mallb2c.mallorder.service.OrderService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {
  private ThreadLocal<OrderSubmitVo> submitVoThreadLocal = new ThreadLocal<>();

  @Autowired
  RabbitTemplate rabbitTemplate;

  @Autowired
  MemberFeignService memberFeignService;

  @Autowired
  CartFeignService cartFeignService;

  @Autowired
  ThreadPoolExecutor executor;

  @Autowired
  WmsFeignService wmsFeignService;

  @Autowired
  StringRedisTemplate redisTemplate;

  @Autowired
  ProductFeignService productFeignService;

  @Autowired
  OrderItemService orderItemService;

  @Autowired
  PaymentInfoService paymentInfoService;

  @Override
  public PageUtils queryPage(Map<String, Object> params) {
    IPage<OrderEntity> page = this.page(
      new Query<OrderEntity>().getPage(params),
      new QueryWrapper<OrderEntity>()
    );

    return new PageUtils(page);
  }

  @Override
  public OrderConfirmVo confirmOrder() throws ExecutionException, InterruptedException {
    OrderConfirmVo confirmVo = new OrderConfirmVo();
    MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

    //获取之前的请求,让每一个线程都共享这个数据
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();//就是用的thread local

    //1、 远程查询所有的收货地址列表
    CompletableFuture<Void> getAddressFuture = CompletableFuture.runAsync(() -> {
      System.out.println("member线程..." + Thread.currentThread().getId());

      RequestContextHolder.setRequestAttributes(requestAttributes);

      List<MemberAddressVo> address = memberFeignService.getAddress(memberRespVo.getId());
      confirmVo.setAddress(address);
    }, executor);

    //2、 远程查询购物车所有的选中的购物项
    CompletableFuture<Void> getCartFuture = CompletableFuture.runAsync(() -> {
      System.out.println("cart线程..." + Thread.currentThread().getId());

      RequestContextHolder.setRequestAttributes(requestAttributes);

      List<OrderItemVo> items = cartFeignService.getCurrentUserCartItems();
      confirmVo.setItems(items);
    }, executor).thenRunAsync(() -> {
      // 查询库存
      List<OrderItemVo> items = confirmVo.getItems();
      List<Long> collect = items.stream().map(item -> item.getSkuId()).collect(Collectors.toList());
      R r = wmsFeignService.getSkusHasStock(collect);
      List<SkuStockVo> data = r.getData(new TypeReference<List<SkuStockVo>>() {
      });
      if (data != null && data.size() > 0) {
        Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(SkuStockVo::getSkuId, SkuStockVo::getHasStock));
        confirmVo.setStocks(map);
      }
    }, executor);


    //3、 查询用户积分
    Integer integration = memberRespVo.getIntegration();
    confirmVo.setIntegration(integration);

    //4、 其它数据自动计算

    //5、防重令牌
    String token = UUID.randomUUID().toString().replace("-", "");
    redisTemplate.opsForValue().set(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId(), token, 30, TimeUnit.MINUTES);
    confirmVo.setOrderToken(token);

    CompletableFuture.allOf(getAddressFuture, getCartFuture).get();

    return confirmVo;
  }

  //本地事务,在分布式系统中,只能控制住自己的回滚,控制不了其他服务的回滚
  //分布式事务: 解决的最大问题 是 网络问题
  @Transactional
  @Override
  public SubmitOrderResponseVo submitOrder(OrderSubmitVo vo) {
    submitVoThreadLocal.set(vo);
    SubmitOrderResponseVo responseVo = new SubmitOrderResponseVo();
    responseVo.setCode(0); //默认为0 没有异常
    MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

    String orderToken = vo.getOrderToken();
    String redisOrderTokenKey = OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId();

    //↓验证令牌【令牌的对比和删除必须保证原子性】
    /*String redisOrderToken = redisTemplate.opsForValue().get(redisOrderTokenKey);
    if (orderToken != null && orderToken.equals(redisOrderToken)) {
      //令牌通过
      redisTemplate.delete(OrderConstant.USER_ORDER_TOKEN_PREFIX + memberRespVo.getId());
    }else {
      //不通过
    }*/
    //下面脚本返回 0 代表检验失败,1代表成功(删除key成功)
    String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";
    Long result = redisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList(redisOrderTokenKey), orderToken);
    if (result == 0L) {
      //令牌验证失败
      responseVo.setCode(1);
      return responseVo;
    } else {
      //令牌验证成功
      //下单: 去创建订单, 验令牌, 验价格, 锁库存...
      //1、 创建订单、订单项等信息
      OrderCreateTo order = createOrder();
      //2、 验价
      BigDecimal payAmount = order.getOrder().getPayAmount();
      BigDecimal payPrice = vo.getPayPrice();
      if (Math.abs(payAmount.subtract(payPrice).doubleValue()) < 0.01) {
        /** step 1
         * 这里如果出问题 没有隐患 只涉及到自身 自身会回滚*/
        //3、 验价成功,保存订单
        saveOrder(order);
        //4、 库存锁定, 只要有异常就回滚订单数据
        // 订单号、订单项(skuId、skuName,num)
        WareSkuLockVo lockVo = new WareSkuLockVo();
        lockVo.setOrderSn(order.getOrder().getOrderSn());
        List<OrderItemVo> locks = order.getOrderItems().stream().map(item -> {
          OrderItemVo itemVo = new OrderItemVo();
          itemVo.setSkuId(item.getSkuId());
          itemVo.setCount(item.getSkuQuantity());
          itemVo.setTitle(item.getSkuName());
          return itemVo;
        }).collect(Collectors.toList());
        lockVo.setLocks(locks);
        /** step 2
         * 库存成功了,但是网络原因超时了,订单回滚,库存不回滚*/
        //远程锁库存
        R r = wmsFeignService.orderLockStock(lockVo);
        if (r.getCode() == 0) {
          //锁成功了
          responseVo.setOrder(order.getOrder());

          //todo 远程扣减积分

          /** step 3
           * 由于之前远程锁库存已经结束了, 这里即使异常了也没法回滚(如果单单只是使用本地事务)*/
          //int a = 10 / 0; //← 模拟异常

          //订单创建成功 发送消息给MQ
          rabbitTemplate.convertAndSend("order-event-exchange", "order.create.order", order.getOrder());

          return responseVo;
        } else {
          //锁失败了
          String msg = (String) r.get("msg");
          throw new NoStockException(msg); //回滚
          //responseVo.setCode(3); //库存出现问题
          //return responseVo;
        }

      } else {
        responseVo.setCode(2); //验价出现问题
        return responseVo;
      }
    }
  }

  @Override
  public OrderEntity getOrderByOrderSn(String orderSn) {
    OrderEntity orderEntity = this.getOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
    return orderEntity;
  }

  @Override
  public void closeOrder(OrderEntity entity) {
    //查询当前这个订单的最新状态
    OrderEntity orderEntity = this.getById(entity.getId());
    if (orderEntity.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()/*←未付款*/) {
      //关单
      OrderEntity update = new OrderEntity(); //因为我们是从mq中拿的实体,但经过这么长时间,该实体实际在数据库中可能有些字段已经发生变化,故我们应确保更改的只是status字段,so 这里创建了个新的实体对象用于更新
      update.setId(entity.getId());
      update.setStatus(OrderStatusEnum.CANCLED.getCode());
      this.updateById(update);
      OrderTo orderTo = new OrderTo();
      BeanUtils.copyProperties(orderEntity, orderTo);

      //发给MQ一个
      rabbitTemplate.convertAndSend("order-event-exchange", "order.release.other", orderTo/*, new CorrelationData()*//** ← */);
      /**↑tip 如何保证消息可靠性-消息丢失: 没有成功发送给mq服务器*/
      //需要保证消息一定会发送出去
      //每一个消息都可以做好日志记录(即 存到数据库中 `mq_message`)
      //然后定期扫描数据库将失败的消息再发送一遍
      //mq服务器接受到会调用 confirm 给客户端回复,这样客户端就能在数据库中将这条消息的状态标识为 已抵达
      //如果没有接收到mq服务器发回的确认信息, 说明可能因为网络问题没有成功投递, 或则说已经投递成功, 但mq发回的回执消息因为网络原因我们这边没接收到
      //不论哪种情况, 我们这里都会等到下次定时扫描, 再次向mq发送该条消息
      //直到接受到mq的确认回执
    }
  }

  @Override
  public PayVo getOrderPay(String orderSn) {
    PayVo payVo = new PayVo();
    OrderEntity order = this.getOrderByOrderSn(orderSn);
    //我们从数据库里取出来是四位数,但支付时实际上只支持两位
    BigDecimal amount = order.getPayAmount().setScale(2, BigDecimal.ROUND_UP/*←向上进入,只要有就进1,而不是四舍五入*/);
    payVo.setTotal_amount(amount.toString()); //订单的金额
    payVo.setOut_trade_no(order.getOrderSn()); //订单号

    //todo 购买多件商品时 支付时的title怎么显示 这里是用了第一件商品的名字
    List<OrderItemEntity> itemEntities = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", orderSn));
    OrderItemEntity entity = itemEntities.get(0);
    payVo.setSubject(entity.getSkuName()); //订单的主题

    //todo 备注怎么写？ 这里是用第一件商品的销售属性
    payVo.setBody(entity.getSkuAttrsVals()); //订单的备注
    return payVo;
  }

  @Override
  public PageUtils queryPageWithItem(Map<String, Object> params) {
    MemberRespVo memberRespVo = LoginUserInterceptor.loginUser.get();

    IPage<OrderEntity> page = this.page(
      new Query<OrderEntity>().getPage(params),
      new QueryWrapper<OrderEntity>().eq("member_id", memberRespVo.getId()).orderByDesc("id")
    );

    List<OrderEntity> collect = page.getRecords().stream().map(order -> {
      List<OrderItemEntity> itemEntities = orderItemService.list(new QueryWrapper<OrderItemEntity>().eq("order_sn", order.getOrderSn()));
      order.setItemEntities(itemEntities);
      return order;
    }).collect(Collectors.toList());

    page.setRecords(collect);

    return new PageUtils(page);
  }

  @Override
  public String handlePayResult(PayAsyncVo vo) {
    //1、 保存交易流水
    PaymentInfoEntity infoEntity = new PaymentInfoEntity();
    infoEntity.setAlipayTradeNo(vo.getTrade_no());
    infoEntity.setOrderSn(vo.getOut_trade_no());
    infoEntity.setPaymentStatus(vo.getTrade_status());
    infoEntity.setCallbackTime(vo.getNotify_time());
    paymentInfoService.save(infoEntity);
    //2、 修改订单状态
    if (vo.getTrade_status().equals("TRADE_SUCCESS") || vo.getTrade_status().equals("TRADE_FINISHED")) {
      //支付成功
      String outTradeNo = vo.getOut_trade_no();
      this.baseMapper.updateOrderStatus(outTradeNo, OrderStatusEnum.PAYED.getCode());
    }
    return "success";
  }

  @Override
  public void createSeckillOrder(SeckillOrderTo entity) {
    //TODO 保存订单信息
    OrderEntity orderEntity = new OrderEntity();
    orderEntity.setOrderSn(entity.getOrderSn());
    orderEntity.setMemberId(entity.getMemberId());
    orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
    BigDecimal multiply = entity.getSeckillPrice().multiply(new BigDecimal("" + entity.getNum()));
    orderEntity.setPayAmount(multiply);
    this.save(orderEntity);

    //TODO 保存订单项信息
    OrderItemEntity orderItemEntity = new OrderItemEntity();
    orderItemEntity.setOrderSn(entity.getOrderSn());
    orderItemEntity.setRealAmount(multiply);
    //todo
    //productFeignService.getSpuInfoBySkuId()
    orderItemEntity.setSkuQuantity(entity.getNum());

    orderItemService.save(orderItemEntity);
  }


  private void saveOrder(OrderCreateTo order) {
    OrderEntity orderEntity = order.getOrder();
    orderEntity.setModifyTime(new Date());
    this.save(orderEntity);

    List<OrderItemEntity> orderItems = order.getOrderItems();
    orderItemService.saveBatch(orderItems);
  }

  private OrderCreateTo createOrder() {
    OrderCreateTo createTo = new OrderCreateTo();

    //1. 创建订单
    //1.1 生成订单号
    String orderSn = IdWorker.getTimeId();
    //1.2
    OrderEntity orderEntity = buildOrder(orderSn);

    //2. 获取到所有订单项
    List<OrderItemEntity> orderItemEntities = buildOrderItems(orderSn);

    //3. 计算价格积分等信息
    computePrice(orderEntity, orderItemEntities);

    //4. 会员信息
    MemberRespVo loginUser = LoginUserInterceptor.loginUser.get();
    orderEntity.setMemberId(loginUser.getId());

    //设置订单相关状态
    orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
    orderEntity.setAutoConfirmDay(7);
    orderEntity.setDeleteStatus(0);

    createTo.setOrder(orderEntity);
    createTo.setOrderItems(orderItemEntities);

    return createTo;
  }

  private void computePrice(OrderEntity orderEntity, List<OrderItemEntity> orderItemEntities) {
    //1、 订单价格相关
    //订单总额
    BigDecimal total = new BigDecimal("0.0");
    BigDecimal coupon = new BigDecimal("0.0");
    BigDecimal integration = new BigDecimal("0.0");
    BigDecimal promotion = new BigDecimal("0.0");
    BigDecimal gift = new BigDecimal("0.0");
    BigDecimal growth = new BigDecimal("0.0");
    for (OrderItemEntity entity : orderItemEntities) {
      total = total.add(entity.getRealAmount());
      coupon = coupon.add(entity.getCouponAmount());
      integration = integration.add(entity.getIntegrationAmount());
      promotion = promotion.add(entity.getPromotionAmount());
      gift = gift.add(new BigDecimal(entity.getGiftIntegration().toString()));
      growth = growth.add(new BigDecimal(entity.getGiftGrowth().toString()));
    }
    orderEntity.setTotalAmount(total);
    //应付总额
    orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
    orderEntity.setPromotionAmount(promotion);
    orderEntity.setCouponAmount(coupon);
    orderEntity.setIntegrationAmount(integration);

    //设置积分信息
    orderEntity.setIntegration(gift.intValue());
    orderEntity.setGrowth(growth.intValue());
  }

  private OrderEntity buildOrder(String orderSn) {
    OrderEntity orderEntity = new OrderEntity();
    orderEntity.setOrderSn(orderSn);

    //获取收货地址信息
    OrderSubmitVo submitVo = submitVoThreadLocal.get();
    //获取收货地址信息
    R r = wmsFeignService.getFare(submitVo.getAddrId());
    FareVo fareResp = r.getData(new TypeReference<FareVo>() {
    });
    //设置运费信息
    orderEntity.setFreightAmount(fareResp.getFare());
    //设置收货人信息
    orderEntity.setReceiverCity(fareResp.getAddress().getCity());
    orderEntity.setReceiverDetailAddress(fareResp.getAddress().getDetailAddress());
    orderEntity.setReceiverName(fareResp.getAddress().getName());
    orderEntity.setReceiverPhone(fareResp.getAddress().getPhone());
    orderEntity.setReceiverPostCode(fareResp.getAddress().getPostCode());
    orderEntity.setReceiverProvince(fareResp.getAddress().getProvince());
    orderEntity.setReceiverRegion(fareResp.getAddress().getRegion());

    return orderEntity;
  }


  /**
   * 构建所有订单项数据
   *
   * @return
   */
  private List<OrderItemEntity> buildOrderItems(String orderSn) {
    //最后确定每个购物项的价格
    List<OrderItemVo> currentUserCartItems = cartFeignService.getCurrentUserCartItems();
    if (currentUserCartItems != null && currentUserCartItems.size() > 0) {
      List<OrderItemEntity> itemEntities = currentUserCartItems.stream().map(item -> {
        OrderItemEntity orderItemEntity = buildOrderItem(item);
        orderItemEntity.setOrderSn(orderSn);
        return orderItemEntity;
      }).collect(Collectors.toList());
      return itemEntities;
    }
    return null;
  }

  /**
   * 构建某一个订单项
   *
   * @param item
   * @return
   */
  private OrderItemEntity buildOrderItem(OrderItemVo item) {
    OrderItemEntity orderItemEntity = new OrderItemEntity();
    //1、 订单信息: 订单号

    //2、 商品的SPU信息
    Long skuId = item.getSkuId();
    R r = productFeignService.getSpuInfoBySkuId(skuId);
    SpuInfoVo spuInfoVo = r.getData(new TypeReference<SpuInfoVo>() {
    });
    orderItemEntity.setSpuId(spuInfoVo.getId());
    orderItemEntity.setSpuBrand(spuInfoVo.getBrandId().toString());
    orderItemEntity.setSpuName(spuInfoVo.getSpuName());
    orderItemEntity.setCategoryId(spuInfoVo.getCatalogId());

    //3、商品的sku信息
    orderItemEntity.setSkuId(item.getSkuId());
    orderItemEntity.setSkuName(item.getTitle());
    orderItemEntity.setSkuPic(item.getImage());
    orderItemEntity.setSkuPrice(item.getPrice());
    //将数组按照指定的分割符连接成一个string
    String skuAttr = StringUtils.collectionToDelimitedString(item.getSkuAttr(), ";");
    orderItemEntity.setSkuAttrsVals(skuAttr);
    orderItemEntity.setSkuQuantity(item.getCount());

    //4、优惠信息

    //5、积分信息
    orderItemEntity.setGiftGrowth(item.getPrice().multiply(new BigDecimal(item.getCount().toString())).intValue());
    orderItemEntity.setGiftIntegration(item.getPrice().multiply(new BigDecimal(item.getCount().toString())).intValue());

    //6、订单项的的价格信息
    orderItemEntity.setPromotionAmount(new BigDecimal("0")); //todo 促销
    orderItemEntity.setCouponAmount(new BigDecimal("0")); // 优惠券
    orderItemEntity.setIntegrationAmount(new BigDecimal("0")); // 积分优惠分解金额
    //当前订单项的实际金额
    BigDecimal origin = orderItemEntity.getSkuPrice().multiply(new BigDecimal(orderItemEntity.getSkuQuantity().toString()));
    BigDecimal realAmount = origin
      .subtract(orderItemEntity.getPromotionAmount())
      .subtract(orderItemEntity.getCouponAmount())
      .subtract(orderItemEntity.getIntegrationAmount());
    orderItemEntity.setRealAmount(realAmount);

    return orderItemEntity;
  }
}
